/* Copyright (C) 2008  Romolo Camplani, Marco Fortunato, Marco Marelli, Guido Rota, Fabio A. Schreiber et al.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.dei.perla.utils.waiter;

import java.util.ArrayList;

import org.dei.perla.component.Component;

/**
 * Oggetto per mettersi in attesa di uno o piu' oggetti Waitable.
*  L'utente dell'oggetto Waiter puu' mettersi in attesa delle notifiche di eventi provenienti
 * da uno qualsiasi degli oggetti Waitable registrati. 
 */
public class Waiter <T extends Component> {

	/**
	 * Elenco degli oggetti Waitable registrati presso il Waiter
	 */
	private final ArrayList<Waitable<T>> pWaitables;
	
	/**
	 * Oggetto usato internamente al Waiter per la sincronizzazione
	 */
	final Object pSyncronizationObject;
	
	/**
	 * Indica se il Waiter e' in stato di funzionamento
	 */
	private boolean pStarted;
		
	/**
	 * Indice dell'ultimo oggetto Waitable che e' stato ritornato
	 */
	private int pLastReturnedWaitableIndex;
	
	/**
	 * Costruttore
	 */
	public Waiter() {
		// Istanzia l'ArrayList in cui verranno inseriti i riferimenti agli Waitable agganciati
		pWaitables = new ArrayList<Waitable<T>>();
		
		// Istanzia l'oggetto di sincronizzazione
		pSyncronizationObject = new Object();
		
		// Inizializza le altre variabili
		pStarted = false;
		pLastReturnedWaitableIndex = -1;
	}
	
	/**
	 * Aggancia un oggetto Waitable al Waiter
	 */
	public void addWaitable(Waitable<T> parWaitable) {
		// Verifica che non sia stato passato un Waitable nullo
		if (parWaitable == null)
			return;
		
		synchronized(pSyncronizationObject) {
			synchronized(parWaitable) {
				// Se l'oggetto Waitable non e' gia' agganciato ad altri Waiter..
				if(parWaitable.getWaiter() == null) {
					// .. lo aggancia a se stesso
					parWaitable.setWaiter(this);
					pWaitables.add(parWaitable);
				}
			}
		}
	}
	
	/**
	 * Sgancia un oggetto Waitable dal Waiter
	 */
	public void removeWaitable(Waitable<T> parWaitable) {	
		// Verifica che non sia stato passato un Waitable nullo
		if (parWaitable == null)
			return;
		
		synchronized(pSyncronizationObject) {
			synchronized(parWaitable) {
				// Se l'oggetto Waitable e' effettivamente agganciato al Waiter..
				if(pWaitables.contains(parWaitable)) {
					// .. lo sgancia
					parWaitable.setWaiter(null);
					this.pWaitables.remove(parWaitable);
				}
			}
		}
	}
	
	/**
	 * Metodo che permette di avviare il Waiter 
	 */
	public void start() {
		synchronized(this.pSyncronizationObject) {
			// Imposta il flag di avviato
			pStarted = true;

			// Risveglia gli eventuali Thread in attesa
			pSyncronizationObject.notifyAll();
		}
	}
	
	/**
	 * Metodo che permette di arrestare l'Waiter. 
	 */
	public void stop() {
		synchronized(this.pSyncronizationObject) {
			// Resetta il flag di avviato
			pStarted = false;
			
			// Risveglia gli eventuali Thread in attesa
			pSyncronizationObject.notifyAll();
		}
	}
	
	/**
	 * Metodo (bloccante) che permette di ottenere il riferimento ad uno degli  
	 * oggetti Waitable registrati che abbia il flag Signaled a true
	 */
	public Waitable<T> waitNext() {
		int tWaitableIndex;
		Waitable<T> tSignaledWaitable = null;
		
		synchronized(pSyncronizationObject) {
			// Determina l'indice dell'ultimo Waitable che il metodo waitNext ha restituito
			tWaitableIndex = pLastReturnedWaitableIndex;			
			
			// Cicla finche' non trova un Waitable segnalato oppure il Waiter viene arrestato..  
			while((pStarted) && (tSignaledWaitable == null)) {
				
				// Scorre tutti gli Waitable registrati
				for(int iCounter = 0; iCounter < pWaitables.size(); ++iCounter) {
					
					// .. determina l'indice del prossimo Waitable da controllare
					if(tWaitableIndex >= pWaitables.size() - 1)
						tWaitableIndex = 0;
					else
						tWaitableIndex++;
					
					// .. e se l'Waitable corrente e' segnalato ..
					if(pWaitables.get(tWaitableIndex).getSignaled()) {
						
						// .. esce dal loop e lo ritorna
						tSignaledWaitable = pWaitables.get(tWaitableIndex);
						this.pLastReturnedWaitableIndex = tWaitableIndex;
						break;
					}	
				}

				// Se non e' stato trovato nessun Waitable segnalato, si mette in attesa
				if(tSignaledWaitable == null) {
					try {
						pSyncronizationObject.wait();
					}
					catch(InterruptedException parException) { 
						//TODO: inserire rilancio eccezione
					}
				}
			}
		}
		
		// Restituisce il riferimento all'Waitable segnalato, oppure null se è stato trovato attivo il flag di terminazione 
		return tSignaledWaitable;
	}
}
